package ru.dc;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import ru.dc.object.CommandListener;
import ru.dc.object.DownloadItem;
import ru.dc.object.DownloadSource;
import ru.dc.object.ServerInfo;
import ru.dc.object.commands.*;
import ru.dc.object.events.download.Connecting;
import ru.dc.object.events.download.DownloadQueueEvent;
import ru.dc.object.events.download.FailedConnect;
import ru.dc.util.FilelistUtils;

import java.io.*;

/**
 * <pre>
 * User: Penkov Vladimir
 * Date: 01.11.2010
 * Time: 16:21:26
 * </pre>
 */
public class ClientConnection extends Connection {

    private String nick;
    private String remoteNick;
    private String key;
    private int localDirectionNumber;
    private int remoteDirectionNumber;
    private Direction.Constants remoteOperation;
    private DownloadItem currentDownloadItem;
    private ServerInfo hubInfo;
    private Log logger = LogFactory.getLog(getClass());
    private boolean waitingRawData = false;

    private OutputStream out;
    private long dataWritten;
    private File targetFile;

    @Override
    protected void beforeConnect() {
        super.beforeConnect();

        CommandDispatchingThread.getInstance().addCommandListener(new CommandListener() {
            public void commandRecieved(Command command) {

                if (command.getConnection()!=ClientConnection.this) {
                    return;
                }

                if (command instanceof MyNick) {
                    MyNick myNick = (MyNick) command;
                    setRemoteNick(myNick.getNick());
                }

                else if (command instanceof Lock) {

                    Lock lockCmd = (Lock) command;
                    String lockStr = lockCmd.getLock();
                    setKey(Lock.convertLockToKey(lockStr));

                    if (lockCmd.isSupportsExtended()) {
                        Supports supports = new Supports();
                        supports.setFeatures("MiniSlots XmlBZList ADCGet TTHL TTHF GetZBlock ZLIG");
                        sendCommand(supports);
                    }


                    Direction direction = new Direction();
                    direction.setOperation(Direction.Constants.DOWNLOAD);
                    if (Direction.Constants.UPLOAD.equals(getRemoteOperation())) {
                        direction.setRandom(0x7FFF);
                        setLocalDirectionNumber(0x7FFF);
                    }
                    else {
                        setLocalDirectionNumber(direction.getRandom());
                    }
                    sendCommand(direction);

                    Key key = new Key();
                    key.setKey(getKey());
                    sendCommand(key);


                }


                else if (command instanceof Direction) {

                    Direction direction = (Direction) command;
                    setRemoteDirectionNumber(direction.getRandom());
                    setRemoteOperation(direction.getOperation());



                }


                else if (command instanceof Key) {//Последняя команда в приветствии
                    //проверяем, имеем ли мы возможность начать загрузку первыми
                    if (getRemoteOperation().equals(Direction.Constants.UPLOAD) || getRemoteDirectionNumber()<getLocalDirectionNumber()) {
                        //если да, то запрашиваем очередной файл для загрузки
                        startDownloadingItems();
                    }
                }
                else if (command instanceof FileLength) {
                    FileLength length = (FileLength) command;
                    currentDownloadItem.setLength(length.getLength());
                    logger.trace("Got length for "+ currentDownloadItem.getFilename()+": "+length.getLength());

                    Send send = new Send();
                    waitingRawData = true;

                    Application.getApplication().getDownloadQueue().markActive(currentDownloadItem);



                    if (!targetFile.exists()) {
                        try {
                            targetFile.createNewFile();
                        } catch (IOException e) {
                            logger.error("Can't create file "+targetFile.getAbsolutePath(), e);
                        }
                    }

                    try {
                        out = new BufferedOutputStream(new FileOutputStream(targetFile, true));
                    } catch (FileNotFoundException e) {
                        logger.error("Can't create output stream for "+targetFile.getAbsolutePath(), e);
                    }

                    sendCommand(send);
                }
            }
        });


    }

    protected void startDownloadingItems() {
        if (out!=null) {
            try {
                out.close();
            } catch (IOException e) {
                logger.error("Can't close output stream for file: "+targetFile, e);
            }
        }
        out = null;
        targetFile = null;
        dataWritten = 0;

        DownloadSource source = new DownloadSource();
        source.setNick(getRemoteNick());
        source.setHub(getHubInfo());

        currentDownloadItem = Application.getApplication().getDownloadQueue().findNextDownload(source);

        if (currentDownloadItem !=null) {

            Connecting connecting = new Connecting();
            connecting.setServerInfo(getServerInfo());
            connecting.setItem(currentDownloadItem);
            Application.getApplication().getHubEventsTranslator().fireHubEventOccured(connecting);

            logger.debug("Starting download "+ currentDownloadItem.getFilename());

            String originalFilename = currentDownloadItem.getFilename();
            String filename = FilenameUtils.getName(originalFilename);

            //если запрашиваем список файлов, то добавим префикс - имя хаба и имя пользователя,
            //чтобы потом отличать списки друг от друга
            if (filename.equals("files.xml.bz2")) {
                filename = FilelistUtils.generateIndividualFilelistName(currentDownloadItem.getSource());
                File parentDir = FilelistUtils.getFilelistsDir();
                targetFile = new File(parentDir, filename);

            }
            else {
                String downloadDirName = Application.getApplication().getSettings().getDownloadDir();
                File downloadDir = new File(downloadDirName);
                if (!StringUtils.isEmpty(currentDownloadItem.getTargetDir())) {
                    downloadDir = new File(downloadDir, currentDownloadItem.getTargetDir());
                    downloadDir.mkdirs();
                }
                targetFile = new File(downloadDir, filename);
            }

            if (!targetFile.exists()) {
                dataWritten = 0;
            }
            else {
                dataWritten = targetFile.length();
            }

            Get get = new Get();
            get.setFilename(currentDownloadItem.getFilename());
            get.setOffset(dataWritten+1);
            sendCommand(get);
        }
        else {
            Application.getApplication().getDownloadQueue().releaseDownloadSource(source);
            close();
        }
    }


    @Override
    protected void afterConnect() {


        super.afterConnect();
        final MyNick nick = new MyNick();
        nick.setNick(getNick());
        sendCommand(nick);

        Lock lock = new Lock();
        lock.setLock("EXTENDEDPROTOCOLABCABCABCABCABCABC");
        lock.setPk("DCPLUSPLUS0.668ABCABC");
        sendCommand(lock);

    }


    @Override
    protected void onSocketCreateError(IOException e) {
        super.onSocketCreateError(e);

        FailedConnect failedConnect = new FailedConnect();
        failedConnect.setServerInfo(getServerInfo());
        failedConnect.setItem(currentDownloadItem);
        Application.getApplication().getHubEventsTranslator().fireHubEventOccured(failedConnect);

        Application.getApplication().getConnectionManager().removeConnection(getServerInfo());

    }

    @Override
    protected void parseReadenData(byte[] buffer, int readen) throws UnsupportedEncodingException {
        if (!waitingRawData) {
            super.parseReadenData(buffer, readen);
        }
        else {
            try {
                byte[] actualBuffer;
                if (readen==buffer.length) {
                    actualBuffer = buffer;
                }
                else {
                    actualBuffer = new byte[readen];
                    System.arraycopy(buffer, 0, actualBuffer,0, readen);
                }

                out.write(actualBuffer);
                logger.trace("Written "+actualBuffer.length+" bytes");
                dataWritten+=readen;

                DownloadQueueEvent e = new DownloadQueueEvent();
                e.setOperation(DownloadQueueEvent.ITEM_DOWNLOADING);
                e.setItem(currentDownloadItem);
                e.setDownloaded(dataWritten);
                Application.getApplication().getDownloadQueue().fireDownloadEvent(e);

            } catch (IOException e) {
                logger.error("Can't write file content for: "+ currentDownloadItem);
            }


            if (dataWritten>= currentDownloadItem.getLength()) {
                logger.info("Downloaded file: "+ currentDownloadItem.getFilename()+", "+dataWritten+" bytes written, expected: "+ currentDownloadItem.getLength());
                waitingRawData = false;
                try {
                    out.flush();
                    out.close();
                } catch (IOException e) {
                    logger.error("Can't close output stream for: "+ currentDownloadItem);
                }
                Application.getApplication().getDownloadQueue().markDownloaded(currentDownloadItem);
                startDownloadingItems();
            }
        }
    }

    public String getNick() {
        return nick;
    }

    public void setNick(String nick) {
        this.nick = nick;
    }

    public String getRemoteNick() {
        return remoteNick;
    }

    public void setRemoteNick(String remoteNick) {
        this.remoteNick = remoteNick;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public int getRemoteDirectionNumber() {
        return remoteDirectionNumber;
    }

    public void setRemoteDirectionNumber(int remoteDirectionNumber) {
        this.remoteDirectionNumber = remoteDirectionNumber;
    }

    public Direction.Constants getRemoteOperation() {
        return remoteOperation;
    }

    public void setRemoteOperation(Direction.Constants remoteOperation) {
        this.remoteOperation = remoteOperation;
    }

    public int getLocalDirectionNumber() {
        return localDirectionNumber;
    }

    public void setLocalDirectionNumber(int localDirectionNumber) {
        this.localDirectionNumber = localDirectionNumber;
    }

    public ServerInfo getHubInfo() {
        return hubInfo;
    }

    public void setHubInfo(ServerInfo hubInfo) {
        this.hubInfo = hubInfo;
    }

    public DownloadItem getCurrentDownloadItem() {
        return currentDownloadItem;
    }

    public void setCurrentDownloadItem(DownloadItem currentDownloadItem) {
        this.currentDownloadItem = currentDownloadItem;
    }

    public boolean isWaitingRawData() {
        return waitingRawData;
    }

    public void setWaitingRawData(boolean waitingRawData) {
        this.waitingRawData = waitingRawData;
    }

    public File getTargetFile() {
        return targetFile;
    }

    public void setTargetFile(File targetFile) {
        this.targetFile = targetFile;
    }

    public OutputStream getOut() {
        return out;
    }

    public void setOut(OutputStream out) {
        this.out = out;
    }
}
